"""
完整数据清洗流程案例
演示从脏数据到干净数据的完整清洗过程
"""

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
from sklearn.impute import KNNImputer
from sklearn.ensemble import IsolationForest
from datetime import datetime, timedelta
import warnings
warnings.filterwarnings('ignore')

# 设置中文显示
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False

print("=" * 80)
print("完整数据清洗流程案例：客户数据清洗")
print("=" * 80)

# ============================================================================
# 步骤1：加载"脏"数据
# ============================================================================

print("\n" + "=" * 80)
print("步骤1：创建包含各种问题的\"脏\"数据")
print("=" * 80)

np.random.seed(42)
n_samples = 1000

# 创建基础数据
data = {
    'customer_id': range(1, n_samples + 1),
    'name': np.random.choice(['张三', '李四', '王五', '赵六', '钱七', '孙八'], n_samples),
    'age': np.random.randint(18, 80, n_samples),
    'gender': np.random.choice(['男', '女', 'M', 'F', 'male', 'female', ''], n_samples),
    'email': [f'user{i}@example.com' if i % 10 != 0 else '' for i in range(n_samples)],
    'phone': [f'138{np.random.randint(10000000, 99999999)}' if i % 8 != 0 else '' 
              for i in range(n_samples)],
    'income': np.random.normal(50000, 20000, n_samples),
    'credit_score': np.random.randint(300, 850, n_samples),
    'purchase_count': np.random.randint(0, 100, n_samples),
    'total_spending': np.random.normal(10000, 5000, n_samples),
    'registration_date': [datetime(2020, 1, 1) + timedelta(days=int(x))
                          for x in np.random.randint(0, 1460, n_samples)],
    'last_purchase_date': [datetime(2023, 1, 1) + timedelta(days=int(x))
                           for x in np.random.randint(0, 365, n_samples)]
}

df_raw = pd.DataFrame(data)

# 引入各种数据质量问题

# 1. 缺失值
missing_indices = np.random.choice(df_raw.index, 100, replace=False)
df_raw.loc[missing_indices, 'age'] = np.nan

missing_indices = np.random.choice(df_raw.index, 150, replace=False)
df_raw.loc[missing_indices, 'income'] = np.nan

missing_indices = np.random.choice(df_raw.index, 80, replace=False)
df_raw.loc[missing_indices, 'credit_score'] = np.nan

# 2. 异常值
outlier_indices = np.random.choice(df_raw.index, 30, replace=False)
df_raw.loc[outlier_indices, 'age'] = np.random.uniform(120, 200, 30)

outlier_indices = np.random.choice(df_raw.index, 40, replace=False)
df_raw.loc[outlier_indices, 'income'] = np.random.uniform(-50000, -10000, 40)

outlier_indices = np.random.choice(df_raw.index, 25, replace=False)
df_raw.loc[outlier_indices, 'total_spending'] = np.random.uniform(100000, 500000, 25)

# 3. 重复值
duplicate_indices = np.random.choice(df_raw.index, 50, replace=False)
df_duplicates = df_raw.loc[duplicate_indices].copy()
df_raw = pd.concat([df_raw, df_duplicates], ignore_index=True)

# 4. 格式不一致（性别）
# 已在创建时混入

# 5. 逻辑错误
# 注册日期晚于最后购买日期
error_indices = np.random.choice(df_raw.index, 20, replace=False)
df_raw.loc[error_indices, 'registration_date'] = df_raw.loc[error_indices, 'last_purchase_date'] + timedelta(days=30)

print(f"原始数据形状: {df_raw.shape}")
print(f"\n数据预览：")
print(df_raw.head(10))

print(f"\n数据类型：")
print(df_raw.dtypes)

# ============================================================================
# 步骤2：数据质量评估
# ============================================================================

print("\n" + "=" * 80)
print("步骤2：数据质量评估")
print("=" * 80)

def assess_data_quality(df, name="数据"):
    """评估数据质量"""
    print(f"\n{name}质量报告")
    print("-" * 80)
    
    # 基本信息
    print(f"数据形状: {df.shape}")
    print(f"内存使用: {df.memory_usage(deep=True).sum() / 1024**2:.2f} MB")
    
    # 缺失值
    print(f"\n缺失值统计:")
    missing = df.isnull().sum()
    missing_pct = df.isnull().mean() * 100
    missing_df = pd.DataFrame({
        '缺失数量': missing[missing > 0],
        '缺失比例(%)': missing_pct[missing > 0]
    }).sort_values('缺失比例(%)', ascending=False)
    if len(missing_df) > 0:
        print(missing_df)
    else:
        print("无缺失值")
    
    # 重复值
    duplicates = df.duplicated().sum()
    print(f"\n重复行数: {duplicates} ({duplicates/len(df)*100:.2f}%)")
    
    # 数据类型
    print(f"\n数据类型分布:")
    print(df.dtypes.value_counts())
    
    return missing_df

# 评估原始数据
quality_before = assess_data_quality(df_raw, "原始数据")

# ============================================================================
# 步骤3：数据清洗
# ============================================================================

print("\n" + "=" * 80)
print("步骤3：开始数据清洗")
print("=" * 80)

df_clean = df_raw.copy()

# 3.1 删除完全重复的行
print("\n3.1 删除重复值")
print("-" * 80)

before_dup = len(df_clean)
df_clean = df_clean.drop_duplicates()
after_dup = len(df_clean)
print(f"删除重复行: {before_dup - after_dup} 行")
print(f"剩余数据: {after_dup} 行")

# 3.2 处理格式不一致
print("\n3.2 处理格式不一致")
print("-" * 80)

# 统一性别格式
print("性别字段原始值分布:")
print(df_clean['gender'].value_counts())

gender_mapping = {
    '男': '男',
    'M': '男',
    'male': '男',
    '女': '女',
    'F': '女',
    'female': '女',
    '': np.nan
}
df_clean['gender'] = df_clean['gender'].map(gender_mapping)
print("\n统一后的性别分布:")
print(df_clean['gender'].value_counts())

# 清理空字符串
df_clean['email'] = df_clean['email'].replace('', np.nan)
df_clean['phone'] = df_clean['phone'].replace('', np.nan)

# 3.3 处理逻辑错误
print("\n3.3 处理逻辑错误")
print("-" * 80)

# 检测注册日期晚于最后购买日期的记录
logic_errors = df_clean['registration_date'] > df_clean['last_purchase_date']
print(f"逻辑错误记录数: {logic_errors.sum()}")

# 修正：将注册日期设为最后购买日期之前
df_clean.loc[logic_errors, 'registration_date'] = \
    df_clean.loc[logic_errors, 'last_purchase_date'] - timedelta(days=365)
print("已修正逻辑错误")

# 3.4 处理异常值
print("\n3.4 处理异常值")
print("-" * 80)

# 年龄异常值处理
age_outliers = (df_clean['age'] < 18) | (df_clean['age'] > 100)
print(f"年龄异常值: {age_outliers.sum()} 个")
df_clean.loc[age_outliers, 'age'] = np.nan

# 收入异常值处理（负值）
income_outliers = df_clean['income'] < 0
print(f"收入异常值（负值）: {income_outliers.sum()} 个")
df_clean.loc[income_outliers, 'income'] = np.nan

# 消费金额异常值处理（IQR方法）
Q1 = df_clean['total_spending'].quantile(0.25)
Q3 = df_clean['total_spending'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 3 * IQR
upper_bound = Q3 + 3 * IQR

spending_outliers = (df_clean['total_spending'] < lower_bound) | \
                    (df_clean['total_spending'] > upper_bound)
print(f"消费金额异常值: {spending_outliers.sum()} 个")

# 使用边界值替换
df_clean.loc[df_clean['total_spending'] < lower_bound, 'total_spending'] = lower_bound
df_clean.loc[df_clean['total_spending'] > upper_bound, 'total_spending'] = upper_bound

# 3.5 处理缺失值
print("\n3.5 处理缺失值")
print("-" * 80)

# 数值型特征缺失值处理
numeric_cols = ['age', 'income', 'credit_score', 'purchase_count', 'total_spending']

# 使用KNN填充
print("使用KNN填充数值型特征...")
knn_imputer = KNNImputer(n_neighbors=5)
df_clean[numeric_cols] = knn_imputer.fit_transform(df_clean[numeric_cols])

# 类别型特征缺失值处理
print("使用众数填充类别型特征...")
df_clean['gender'].fillna(df_clean['gender'].mode()[0], inplace=True)

# 联系方式缺失保留（可能确实没有）
print(f"email缺失: {df_clean['email'].isnull().sum()}")
print(f"phone缺失: {df_clean['phone'].isnull().sum()}")

# 3.6 数据类型转换
print("\n3.6 数据类型转换")
print("-" * 80)

df_clean['age'] = df_clean['age'].astype(int)
df_clean['credit_score'] = df_clean['credit_score'].astype(int)
df_clean['purchase_count'] = df_clean['purchase_count'].astype(int)

print("数据类型已转换")

# 3.7 创建派生特征
print("\n3.7 创建派生特征")
print("-" * 80)

# 客户年龄段
df_clean['age_group'] = pd.cut(df_clean['age'], 
                                bins=[0, 30, 45, 60, 100],
                                labels=['青年', '中年', '中老年', '老年'])

# 收入等级
df_clean['income_level'] = pd.cut(df_clean['income'],
                                   bins=[0, 30000, 60000, 100000, np.inf],
                                   labels=['低收入', '中等收入', '高收入', '超高收入'])

# 客户价值（基于消费和购买次数）
df_clean['customer_value'] = df_clean['total_spending'] * np.log1p(df_clean['purchase_count'])

# 客户活跃度（距离最后购买的天数）
df_clean['days_since_purchase'] = (datetime.now() - df_clean['last_purchase_date']).dt.days
df_clean['is_active'] = df_clean['days_since_purchase'] < 90

print("派生特征已创建")

# ============================================================================
# 步骤4：清洗后数据质量评估
# ============================================================================

print("\n" + "=" * 80)
print("步骤4：清洗后数据质量评估")
print("=" * 80)

quality_after = assess_data_quality(df_clean, "清洗后数据")

# ============================================================================
# 步骤5：可视化对比
# ============================================================================

print("\n" + "=" * 80)
print("步骤5：可视化清洗前后对比")
print("=" * 80)

fig = plt.figure(figsize=(20, 12))
gs = fig.add_gridspec(3, 4, hspace=0.3, wspace=0.3)
fig.suptitle('数据清洗前后对比', fontsize=16, fontweight='bold')

# 1. 数据量对比
ax1 = fig.add_subplot(gs[0, 0])
categories = ['清洗前', '清洗后']
counts = [len(df_raw), len(df_clean)]
ax1.bar(categories, counts, color=['coral', 'lightgreen'], edgecolor='black')
ax1.set_ylabel('记录数')
ax1.set_title('数据量对比', fontweight='bold')
ax1.grid(True, alpha=0.3, axis='y')
for i, v in enumerate(counts):
    ax1.text(i, v, str(v), ha='center', va='bottom')

# 2. 缺失值对比
ax2 = fig.add_subplot(gs[0, 1])
missing_before = df_raw.isnull().sum().sum()
missing_after = df_clean.isnull().sum().sum()
ax2.bar(categories, [missing_before, missing_after], color=['coral', 'lightgreen'], edgecolor='black')
ax2.set_ylabel('缺失值数量')
ax2.set_title('缺失值对比', fontweight='bold')
ax2.grid(True, alpha=0.3, axis='y')
for i, v in enumerate([missing_before, missing_after]):
    ax2.text(i, v, str(v), ha='center', va='bottom')

# 3. 重复值对比
ax3 = fig.add_subplot(gs[0, 2])
dup_before = df_raw.duplicated().sum()
dup_after = df_clean.duplicated().sum()
ax3.bar(categories, [dup_before, dup_after], color=['coral', 'lightgreen'], edgecolor='black')
ax3.set_ylabel('重复行数')
ax3.set_title('重复值对比', fontweight='bold')
ax3.grid(True, alpha=0.3, axis='y')
for i, v in enumerate([dup_before, dup_after]):
    ax3.text(i, v, str(v), ha='center', va='bottom')

# 4. 性别分布
ax4 = fig.add_subplot(gs[0, 3])
gender_clean = df_clean['gender'].value_counts()
ax4.pie(gender_clean.values, labels=gender_clean.index, autopct='%1.1f%%')
ax4.set_title('性别分布（清洗后）', fontweight='bold')

# 5-6. 年龄分布对比
ax5 = fig.add_subplot(gs[1, 0])
ax5.hist(df_raw['age'].dropna(), bins=30, alpha=0.7, edgecolor='black', color='coral')
ax5.set_xlabel('年龄')
ax5.set_ylabel('频数')
ax5.set_title('年龄分布（清洗前）', fontweight='bold')
ax5.grid(True, alpha=0.3)

ax6 = fig.add_subplot(gs[1, 1])
ax6.hist(df_clean['age'], bins=30, alpha=0.7, edgecolor='black', color='lightgreen')
ax6.set_xlabel('年龄')
ax6.set_ylabel('频数')
ax6.set_title('年龄分布（清洗后）', fontweight='bold')
ax6.grid(True, alpha=0.3)

# 7-8. 收入分布对比
ax7 = fig.add_subplot(gs[1, 2])
ax7.hist(df_raw['income'].dropna(), bins=30, alpha=0.7, edgecolor='black', color='coral')
ax7.set_xlabel('收入')
ax7.set_ylabel('频数')
ax7.set_title('收入分布（清洗前）', fontweight='bold')
ax7.grid(True, alpha=0.3)

ax8 = fig.add_subplot(gs[1, 3])
ax8.hist(df_clean['income'], bins=30, alpha=0.7, edgecolor='black', color='lightgreen')
ax8.set_xlabel('收入')
ax8.set_ylabel('频数')
ax8.set_title('收入分布（清洗后）', fontweight='bold')
ax8.grid(True, alpha=0.3)

# 9. 年龄段分布
ax9 = fig.add_subplot(gs[2, 0])
age_group_dist = df_clean['age_group'].value_counts()
ax9.bar(age_group_dist.index, age_group_dist.values, color='skyblue', edgecolor='black')
ax9.set_xlabel('年龄段')
ax9.set_ylabel('人数')
ax9.set_title('年龄段分布', fontweight='bold')
ax9.grid(True, alpha=0.3, axis='y')

# 10. 收入等级分布
ax10 = fig.add_subplot(gs[2, 1])
income_level_dist = df_clean['income_level'].value_counts()
ax10.bar(income_level_dist.index, income_level_dist.values, color='lightcoral', edgecolor='black')
ax10.set_xlabel('收入等级')
ax10.set_ylabel('人数')
ax10.set_title('收入等级分布', fontweight='bold')
ax10.grid(True, alpha=0.3, axis='y')
plt.setp(ax10.xaxis.get_majorticklabels(), rotation=45, ha='right')

# 11. 客户活跃度
ax11 = fig.add_subplot(gs[2, 2])
active_dist = df_clean['is_active'].value_counts()
ax11.pie(active_dist.values, labels=['不活跃', '活跃'], autopct='%1.1f%%',
         colors=['lightcoral', 'lightgreen'])
ax11.set_title('客户活跃度', fontweight='bold')

# 12. 客户价值分布
ax12 = fig.add_subplot(gs[2, 3])
ax12.hist(df_clean['customer_value'], bins=30, alpha=0.7, edgecolor='black', color='purple')
ax12.set_xlabel('客户价值')
ax12.set_ylabel('频数')
ax12.set_title('客户价值分布', fontweight='bold')
ax12.grid(True, alpha=0.3)

plt.savefig('/Users/binming/Desktop/CodeBase/python/机器学习/特征工程/0-数据清洗/完整清洗流程对比.png',
            dpi=300, bbox_inches='tight')
print("清洗前后对比图已保存")

# ============================================================================
# 步骤6：生成数据质量报告
# ============================================================================

print("\n" + "=" * 80)
print("步骤6：生成数据质量报告")
print("=" * 80)

report = f"""
数据清洗报告
{'=' * 80}

1. 数据概况
   - 原始数据: {len(df_raw)} 行 × {len(df_raw.columns)} 列
   - 清洗后数据: {len(df_clean)} 行 × {len(df_clean.columns)} 列
   - 删除记录: {len(df_raw) - len(df_clean)} 行 ({(len(df_raw) - len(df_clean))/len(df_raw)*100:.2f}%)

2. 数据质量改进
   - 缺失值: {missing_before} → {missing_after} (减少 {missing_before - missing_after})
   - 重复值: {dup_before} → {dup_after} (减少 {dup_before - dup_after})
   - 异常值处理: 
     * 年龄异常: {age_outliers.sum()} 个
     * 收入异常: {income_outliers.sum()} 个
     * 消费异常: {spending_outliers.sum()} 个

3. 数据标准化
   - 性别字段: 统一为"男"/"女"
   - 数据类型: 已转换为正确类型
   - 逻辑错误: 已修正 {logic_errors.sum()} 条

4. 特征工程
   - 新增派生特征:
     * age_group: 年龄段分类
     * income_level: 收入等级
     * customer_value: 客户价值
     * days_since_purchase: 距上次购买天数
     * is_active: 是否活跃客户

5. 数据统计（清洗后）
   - 平均年龄: {df_clean['age'].mean():.1f} 岁
   - 平均收入: ¥{df_clean['income'].mean():.0f}
   - 平均信用分: {df_clean['credit_score'].mean():.0f}
   - 活跃客户比例: {df_clean['is_active'].mean()*100:.1f}%

6. 建议
   - 定期清洗数据，保持数据质量
   - 建立数据质量监控机制
   - 记录清洗规则，确保一致性
   - 保留原始数据备份
"""

print(report)

# 保存报告
with open('/Users/binming/Desktop/CodeBase/python/机器学习/特征工程/0-数据清洗/数据清洗报告.txt', 'w', encoding='utf-8') as f:
    f.write(report)
print("\n数据清洗报告已保存")

# 保存清洗后的数据
df_clean.to_csv('/Users/binming/Desktop/CodeBase/python/机器学习/特征工程/0-数据清洗/cleaned_data.csv', 
                index=False, encoding='utf-8')
print("清洗后的数据已保存")

# ============================================================================
# 步骤7：数据清洗最佳实践总结
# ============================================================================

print("\n" + "=" * 80)
print("步骤7：数据清洗最佳实践总结")
print("=" * 80)

print("""
完整数据清洗流程：

1. 数据探索 (Data Exploration)
   ✓ 查看数据结构和类型
   ✓ 统计描述性分析
   ✓ 识别数据质量问题

2. 重复值处理 (Duplicate Handling)
   ✓ 检测完全重复和部分重复
   ✓ 根据业务规则删除或合并
   ✓ 记录处理决策

3. 格式标准化 (Format Standardization)
   ✓ 统一类别值格式
   ✓ 转换数据类型
   ✓ 清理特殊字符和空格

4. 逻辑验证 (Logic Validation)
   ✓ 检查业务逻辑错误
   ✓ 验证数据一致性
   ✓ 修正或标记异常

5. 异常值处理 (Outlier Handling)
   ✓ 使用统计或机器学习方法检测
   ✓ 根据场景选择处理策略
   ✓ 记录异常值位置

6. 缺失值处理 (Missing Value Imputation)
   ✓ 分析缺失机制
   ✓ 选择合适的填充方法
   ✓ 考虑创建缺失指示变量

7. 特征工程 (Feature Engineering)
   ✓ 创建派生特征
   ✓ 特征变换
   ✓ 特征编码

8. 质量验证 (Quality Validation)
   ✓ 对比清洗前后
   ✓ 验证业务逻辑
   ✓ 生成质量报告

9. 文档记录 (Documentation)
   ✓ 记录清洗步骤
   ✓ 保存处理规则
   ✓ 版本控制

10. 持续监控 (Continuous Monitoring)
    ✓ 建立数据质量指标
    ✓ 自动化清洗流程
    ✓ 定期审查和更新

关键原则：
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ 保留原始数据备份
✓ 记录所有清洗决策
✓ 结合业务理解
✓ 自动化可重复的步骤
✓ 验证清洗结果
✓ 持续改进清洗流程
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
""")

print("\n" + "=" * 80)
print("案例完成！")
print("=" * 80)
print(f"\n生成的文件：")
print("1. 完整清洗流程对比.png - 可视化对比图")
print("2. 数据清洗报告.txt - 详细清洗报告")
print("3. cleaned_data.csv - 清洗后的数据")
